package com.nononsenseapps.feeder.net

import com.gitee.wsl.store.StateSavedVar
import com.gitee.wsl.jvm.store.mmkv.MMKVStoreServer
import com.nononsenseapps.jsonfeed.NOSET_CONNECT_TIMEOUT_SECS
import com.nononsenseapps.jsonfeed.cachingHttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.*
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.lang.Exception
import java.net.URL
import java.net.URLDecoder
import java.util.concurrent.TimeUnit

class NetManager(@Volatile var client: OkHttpClient = defaultClient) {

    companion object{
        val MUTABLE_SERVER_LIST="mutable_server_list"

        val defaultClient by lazy {cachingHttpClient()}
        val defaultNetManager by lazy { NetManager() }

        var mutableServerList by StateSavedVar(MMKVStoreServer.get(),MUTABLE_SERVER_LIST, emptyList<String>())
    }

    /**
     * To enable caching, you need to call this method explicitly with a suitable cache directory.
     */
    @Synchronized
    fun setup(cacheDir: File?): NetManager {
        client = cachingHttpClient(cacheDir)
        return this
    }

    /**
     * @throws IOException if request fails due to network issue for example
     */
     suspend fun curl(url: URL): String? {
        var result: String? = null
        curlAndOnResponse(url) {
            result = it.body?.string()
        }
        return result
    }

    /**
     * @throws IOException if request fails due to network issue for example
     */
     suspend fun curlAndOnResponse(url: URL, block: (suspend (Response) -> Unit)) {
        val response = getResponse(url)

        if (!response.isSuccessful) {
            throw IOException("Unexpected code $response")
        }

        response.use {
            block(it)
        }
    }

    /**
     * @throws IOException if call fails due to network issue for example
     */
    suspend fun getResponse(url: URL, forceNetwork: Boolean = false,connectTimeoutSecs: Long = NOSET_CONNECT_TIMEOUT_SECS): Response {
        val request = Request.Builder()
                .url(url)
                .cacheControl(CacheControl.Builder()
                        .let {
                            if (forceNetwork) {
                                // Force a cache revalidation
                                it.maxAge(0, TimeUnit.SECONDS)
                            } else {
                                // Do a cache revalidation at most every minute
                                it.maxAge(1, TimeUnit.MINUTES)
                            }
                        }
                        .build())
                .build()

        var clientToUse = if (url.userInfo?.isNotBlank() == true) {
            val parts = url.userInfo.split(':')
            val user = parts.first()
            val pass = if (parts.size > 1) {
                parts[1]
            } else {
                ""
            }

            val decodedUser = URLDecoder.decode(user, "UTF-8")
            val decodedPass = URLDecoder.decode(pass, "UTF-8")
            val credentials = Credentials.basic(decodedUser, decodedPass)

            client.newBuilder()
                    .authenticator(object: Authenticator {
                        override fun authenticate(route: Route?, response: Response): Request? {
                            when {
                                response.request.header("Authorization") != null -> {
                                    return null
                                }
                                else -> {
                                    return response.request.newBuilder()
                                            .header("Authorization", credentials)
                                            .build()
                                }
                            }
                        }
                    })
                    .proxyAuthenticator(object : Authenticator {
                        override fun authenticate(route: Route?, response: Response): Request? {
                            when {
                                response.request.header("Proxy-Authorization") != null -> {
                                    return null
                                }
                                else -> {
                                    return response.request.newBuilder()
                                            .header("Proxy-Authorization", credentials)
                                            .build()
                                }
                            }
                        }

                    })
                    .build()
        } else {
            client
        }

        if(connectTimeoutSecs>0&&TimeUnit.SECONDS.toMillis(connectTimeoutSecs).toInt()!=client.connectTimeoutMillis)
            clientToUse=clientToUse.newBuilder().callTimeout(connectTimeoutSecs,TimeUnit.SECONDS).build()

        Timber.d("new request url:${request.url}")

        return withContext(Dispatchers.IO) {
            clientToUse.newCall(request).execute()
        }
    }

    /**
     * @param url          下载连接
     * @param destFileDir  下载的文件储存目录
     * @param destFileName 下载文件名称
     * @param success, err    下载监听
     */
    suspend fun download(  url:String,   destFileDir:String,   destFileName:String, success: (suspend (String) -> Unit) , err: suspend (e: Exception) -> Unit={}) {
        try {
            curlAndOnResponse(URL(url)) {response->
                var inputStream: InputStream? = null
                val buf = ByteArray(1024 * 8)
                var len = 0
                var fos: FileOutputStream? = null
                val dir = File(destFileDir)

                if (!dir.exists()) {
                    dir.mkdirs()
                }
                val file = File(dir, destFileName)

                try {
                    inputStream = response.body?.byteStream()
                    val total = response.body?.contentLength()
                    var sum: Long = 0

                    fos = FileOutputStream(file)

                    len = inputStream?.read(buf)!!
                    while (len != -1) {
                        sum += len.toLong()
                        fos.write(buf, 0, len)
                        //val finalSum = sum
                        len = inputStream.read(buf)
                    }
                    fos.flush()

                } catch (e: Exception) {
                    e.printStackTrace()
                    err(e)
                } finally {
                    try {
                        response.body?.close()
                        inputStream?.close()
                    } catch (e: IOException) {
                    }
                    try {
                        fos?.close()
                    } catch (e: IOException) {
                    }
                }
                success(file.absolutePath)
            }
        }catch (e: Exception) {
            err(e)
        }
    }

    suspend fun ping(url:URL,connectTimeoutSecs: Long = NOSET_CONNECT_TIMEOUT_SECS):Boolean{
        return try {
            getResponse(url,connectTimeoutSecs=connectTimeoutSecs).isSuccessful
        }catch (e:Exception){
            false
        }
    }
}